//@auther foolbread
//@time 2016-03-09
//@file ApnsHttp2/client/client.go
package client

import (
	"ApnsHttp2/apns"
	aerror "ApnsHttp2/error"
	"ApnsHttp2/log"
	"encoding/base64"
	"encoding/binary"
	fbatomic "github.com/foolbread/fbcommon/atomic"
	"github.com/foolbread/fbcommon/golog"
	fbnet "github.com/foolbread/fbcommon/net"
	fbtime "github.com/foolbread/fbcommon/time"
	"github.com/sluu99/uuid"
	"net"
	"strings"
	"time"
)

func InitClient() {
	golog.Info("initing ApnsHttp2 client...")
	go ti.Start()
}

const HEAD_LEN = 12

const (
	KEEPALIVE_CMD = 0
)

type Head struct {
	Magic  uint32
	Length uint32
	Cmd    uint32
}

func UnmarshalHead(d []byte) *Head {
	h := new(Head)
	h.Magic = binary.BigEndian.Uint32(d[0:4])
	h.Length = binary.BigEndian.Uint32(d[4:8])
	h.Cmd = binary.BigEndian.Uint32(d[8:12])

	return h
}

func RecvPacket(c net.Conn, d []byte) (*Head, error) {
	err := fbnet.ReadByCnt(c, d[:HEAD_LEN])

	if err != nil {
		return nil, err
	}

	h := UnmarshalHead(d[:HEAD_LEN])
	if h.Length > uint32(len(d)) {
		return h, aerror.ErrPacketTooLong
	}

	if h.Length > HEAD_LEN {
		err = fbnet.ReadByCnt(c, d[HEAD_LEN:h.Length])
		if err != nil {
			return h, err
		}
	}

	return h, nil
}

func SendPacket(c net.Conn, d []byte) error {
	return fbnet.WriteByCnt(c, d)
}

//////////////////////////////////////////////////////////////////////////////////////////
var ti *fbtime.Timer = fbtime.New(1 * time.Second)
var checkTime time.Duration = 10 * time.Second

const MESSAGE_ARRAY_LEN = 255

type Client struct {
	con   net.Conn
	msgs  [MESSAGE_ARRAY_LEN]*messageMap
	alive fbatomic.AtomicBool
}

func NewClient(c net.Conn) *Client {
	r := new(Client)
	r.con = c
	for i := 0; i < MESSAGE_ARRAY_LEN; i++ {
		r.msgs[i] = newMessageMap()
	}
	r.alive.Set(true)

	return r
}

func (c *Client) checkAlive() {
	if !c.alive.Get() {
		c.con.Close()
		golog.Info("client:", c.con.RemoteAddr(), "exit...")
		return
	}

	c.alive.Set(false)
	ti.NewTimer(checkTime, c.checkAlive)
}

func (c *Client) Run() {
	//ti.NewTimer(checkTime, c.checkAlive)
	for {
		var buf [4096]byte
		head, err := RecvPacket(c.con, buf[:])
		if err != nil {
			golog.Error("client:", c.con.RemoteAddr().String(), "error:", err)
			break
		}

		//c.alive.Set(true)
		if head.Cmd == KEEPALIVE_CMD {
			//c.alive.Set(true)
			continue
		}

		go c.handlerPushReq(head, buf[:])
	}

	c.con.Close()
}

func (c *Client) handlerPushReq(h *Head, d []byte) {
	var msg apns.ApnsMessage
	err := msg.UnmarshalJSON(d[HEAD_LEN:h.Length])
	if err != nil {
		golog.Info(string(d))
		golog.Error(err)
		return
	}

	binary.BigEndian.PutUint32(d[8:12], h.Cmd+1)
	SendPacket(c.con, d[:h.Length])

	if strings.Index(msg.M.Token, "nopush_") >= 0 {
		log.LogFile().Errorf("error:invalid_token type:%d sn:%s token:%s", msg.M.Type, msg.M.Sn, msg.M.Token)
		return
	}

	msg.Uid = uuid.Rand()
	msg.Uidstr = msg.Uid.Hex()
	mMap := c.msgs[msg.Uid[0]%MESSAGE_ARRAY_LEN]
	msg.M.DataByte, err = base64.StdEncoding.DecodeString(msg.M.Data)
	if err != nil {
		golog.Error("sn:", msg.M.Sn, "decode base64 error:", err)
		return
	}

	mMap.r.Lock()
	mMap.m[msg.Uidstr] = newMessageStatus(msg.M.Sn, 0)
	mMap.r.Unlock()

	//push apns
	apns.GetApns().PushMsg(&msg, c.handlerPushRes)
}

func (c *Client) handlerPushRes(msg *apns.ApnsMessage, status int) {
	if status > 0 {
		mMap := c.msgs[msg.Uid[0]%MESSAGE_ARRAY_LEN]
		mMap.r.Lock()
		delete(mMap.m, msg.Uidstr)
		mMap.r.Unlock()

		log.LogFile().Infof("type:%d sn:%s token:%s status:%d", msg.M.Type, msg.M.Sn, msg.M.Token)
	}

	//send response
}
